package cn.scs.impl;


import cn.scs.common.Config;
import cn.scs.common.FileInfo;
import cn.scs.common.MetaOpCode;
import cn.scs.component.CycleTask;
import cn.scs.component.DatanodeFinder;
import cn.scs.component.MetaHandler;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.List;

public class MetadataServer {
    private static final Logger log = LogManager.getLogger(MetadataServer.class);
    private ServerSocket serverSocket;
    int socketCounts = 0;   // Socket 连接数目 计数
    private final Map<String, String> fileToStorageNode; // 文件路径到存储节点名称的映射
    private final MetaHandler metaHandler = new MetaHandler();  // 元数据IO处理器
    private final LinkedHashMap<String,StorageNode> storageNodes = new LinkedHashMap<>();
    private final LinkedHashMap<String,Long> storageNodesUpTime = new LinkedHashMap<>();
    private Map<String, String> fileOwners;
    private final Map<String, FileInfo> fileSystem; // 文件元数据，文件路径到文件信息的映射

    private boolean isRunning;

    // 传入 分布式文件系统的image文件路径 及 worker路径 实例化MetaServer
    public MetadataServer(String imagePath, String workerPath) {
        //初始化所有slave节点IP信息
        DatanodeFinder datanodeFinder = new DatanodeFinder("datanodes.properties");
        fileSystem = metaHandler.openImage(Config.META_STORE_PATH).getFileSystem();
        // 处理读取并注册 所有slave
        Map<String, String> hostIpPair = Config.worker_get();
        fileToStorageNode = new HashMap<>();
        //初始化dataNode节点信息
        for (String host: hostIpPair.keySet()) {
            storageNodes.put(host, new StorageNode(host));
            System.out.println("SlaveNode <"+host+"> Registered! \tIP: "+hostIpPair.get(host) + "\tPort:" + datanodeFinder.getDatanodePort(host));
        }
        try {
            serverSocket = new ServerSocket(Config.META_SERVRE_PORT);
        } catch (IOException e) {
            log.info(e);}
    }

    private void serve() {
        System.out.println("MetaServer is running...");
        isRunning = true;
        while (isRunning) {
            Socket clientSocket;
            try {
                // 接受客户端连接
                clientSocket = serverSocket.accept();
                socketCounts += 1;
                System.out.println(">>> Accepted Socket updated: " + socketCounts);
                //调用process函数处理请求
                new processHandler(clientSocket).start();
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
        }
    }

    // 多线程
    private class processHandler extends Thread {
        private final Socket clientSocket;
        public processHandler(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }
        @Override
        public void run() {
            try {
                DataInputStream in = new DataInputStream(clientSocket.getInputStream());
                DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());

                MetaOpCode op = MetaOpCode.read(in);
                process(this.clientSocket,op,in,out);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            } finally {
                try {
                    if (clientSocket != null) {
                        clientSocket.close();
                    }
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }
    }
    //
    /**
     * 处理客户端元数据请求并返回响应,处理的信息包括：
     * 注册和心跳： 数据服务器需要在启动时向元数据服务器注册自己，告知自己的可用性。随后，数据服务器可以定期发送心跳消息，以通知元数据服务器它的状态。
     * 文件信息交换： 数据服务器可以定期向元数据服务器汇报文件信息，例如已存储的文件列表、文件大小等。元数据服务器可以根据这些信息维护文件目录和存储位置信息。
     * 文件副本管理： 元数据服务器可以指导数据服务器在不同的存储节点上创建文件的副本，以增加数据冗余和容错性。
     * 文件删除和迁移： 元数据服务器可以通知数据服务器删除特定文件，也可以指导文件从一个数据服务器迁移到另一个数据服务器。
     * 数据块分配： 元数据服务器可以负责指导数据服务器如何分配文件的不同数据块，以便实现数据的分布式存储和访问。
     * 一致性和同步： 当文件信息或状态发生变化时，确保元数据服务器和数据服务器之间的信息是一致的。你可能需要考虑使用分布式一致性协议（如 Paxos、Raft）来实现这一点。
     * @throws IOException
     */
    protected final void process(Socket clientSocket, MetaOpCode op, DataInputStream in, DataOutputStream out) {
        boolean socketRunning = true;  // 保持连接, 主要对 DFS_CLIENT_EXIT结束码 进行响应 以结束该 Socket 的分支任务
        while(socketRunning) {
            System.out.println("Operation Code >>> " + op );
            switch (op) {
                case HEART_BEAT:
                    handleHeartBeat(in, out);
                    break;
                case CREATE_FILE:
                    createFile(in, out);
                    break;
                case APPLY_BLOCK:
                    applyBlock(in, out);
                    break;
                case DOWNLOAD_FILE:
                    downloadFile(in, out);
                    break;
                case RENAME_FILE:
                    renameFile(in,out);
                    break;
                case DEL_FILE:
                    deleteFile(in,out);
                    break;
                case LIST_FILE:
                    listFile(in, out);
                    break;
                case DFS_CLIENT_EXIT:
                    if (DFS_Client_Exit(in, out)) {
                        System.out.println("Process Ended... DFS client exit!");
                        socketCounts -= 1;
                        socketRunning = false; // 退出成功，该 Socket process 应被切断
                    }
                    break;
                default:
                    System.out.println("Unknown op " + op + " in data stream");
            }
            try {
                if (socketRunning) {
                    op = MetaOpCode.read(in);   // 下一轮循环更新op
                }
            } catch (IOException e) {
                e.printStackTrace();
                try {
                    clientSocket.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                break;
            }
        }
    }

    private void createFile(DataInputStream in, DataOutputStream out) {
        try {
            String path = in.readUTF(); // 读取客户端发送路径
            String owner = in.readUTF(); // 读取客户端发送用户
            boolean isDir = in.readBoolean(); // 是否为目录

            log.info(new Date() +" before createFile." );
            //对元数据进行操作
            FileInfo fi = create(path,owner,isDir);
            // 如果无fi 直接返回错误码    -HUADD
            if (fi == null) {
                out.writeInt(-1);
                out.writeUTF("File Existed!");
                out.flush();
                return;
            }
            log.info(new Date() +" after createFile." );
            // 返回code
            out.writeInt(0);
            out.flush();
            //获取--初始--存储节点信息
            String nodeName = getNewStorageNode(0);
            //test nodeName
//            System.out.println(nodeName);
            String localFileId = UUID.randomUUID().toString();
            fi.getLocations().add(nodeName+":"+localFileId);
            out.writeUTF(nodeName+":"+localFileId);
            //TODO 2023年12月7日 将UUID与datanodeName做好映射 实现分块
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
            try {
                out.writeInt(-1);
                out.writeUTF(e.getMessage());
                out.flush();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
        System.out.println(new Date() +" createFile done." );
    }

    // 检查 并 生成块UUID
    private void applyBlock(DataInputStream in, DataOutputStream out) {
        try {
            String path = in.readUTF(); // 读取客户端发送路径
            String owner = in.readUTF(); // 读取客户端发送用户
            String lastUUID = in.readUTF();  // 读取客户端发送UUID
            FileInfo fi = fileSystem.getOrDefault(path, null);  // 找不到FileInfo则返回null
            List<String> locations = fi.getLocations();
            if (fi != null && locations.get(locations.size()-1).equals(lastUUID)) {   // 找到FileInfo，并核对UUID
                String nodeName = getNewStorageNode(0);
                //test nodeName
                System.out.println(nodeName);
                String localFileId = UUID.randomUUID().toString();
                locations.add(nodeName+":"+localFileId);
                System.out.println("new block: " + nodeName + ":" + localFileId);
                out.writeInt(0);
                out.writeUTF(nodeName+":"+localFileId);
            } else {
                out.writeInt(-1);
                out.writeUTF("Meta Data mismatch!");
            }
            out.flush();    // flush输出
        } catch (IOException e) {
            e.printStackTrace();
            try {
                out.writeInt(-1);
                out.writeUTF(e.getMessage());
                out.flush();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    // 返回 文件 locations
    private void downloadFile(DataInputStream in, DataOutputStream out) {
        try {
            String path = in.readUTF(); // 读取客户端发送路径
            String owner = in.readUTF(); // 读取客户端发送用户
            // 一些权限检查
            sendLocation(path, out);
            out.writeInt(0);
            out.writeUTF("Download");
            System.out.println(path + "Download success!" );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 返回 文件 locations
    private void sendLocation(String path, DataOutputStream out) {
        try {
            List<String> locations = fileSystem.get(path).getLocations();
            out.writeInt(locations.size()); // 告知locations长度
            for (String loc: locations) {
                out.writeUTF(loc);
            }   // 传输完毕
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//    // 添加文件元数据
//    public void addFileMetadata(String filePath, long fileSize, String storageNode) {
//        FileInfo fileInfo = new FileInfo(filePath, fileSize);
//        fileSystem.put(filePath, fileInfo);
//        fileToStorageNode.put(filePath, storageNode);
//    }

    // 创建文件或目录
    public FileInfo create(String path, String owner, boolean isDirectory) throws IOException {
        FileInfo fileInfo = null;
        if (!fileSystem.containsKey(path)) {
            String parentPath = getParentPath(path);
            if (!fileSystem.containsKey(parentPath) && !parentPath.equals("/")) {
                System.out.println("Parent directory " + parentPath + " does not exist.");
                create(parentPath,  owner, true);
            }
            //要创建目录或文件的父目录->存在
            if (fileSystem.containsKey(parentPath) || parentPath.equals("/")) {
                FileInfo parentInfo = fileSystem.get(parentPath);
                fileInfo = new FileInfo(path, owner, isDirectory, parentInfo);
                fileSystem.put(path, fileInfo);
                //待实现创建文件操作
                System.out.println((isDirectory ? "Directory" : "File") + " " + path + " created by " + owner);
            }
        } else {
            System.out.println((isDirectory ? "Directory" : "File") + " " + path + " already exists.");
            //fileInfo = fileSystem.get(path);  // 已存在不可被在创建    返回null  -HUADD
        }
        String[] splits = path.split("/");
        fileInfo.setFileName(splits[splits.length-1]); // 设置文件名称
        return fileInfo;
    }
    private void deleteFile(DataInputStream in, DataOutputStream out) {
        try {
            String path = in.readUTF(); // 读取客户端发送路径
            String owner = in.readUTF(); // 读取客户端发送用户
//            byte code = in.readByte();
            log.info(new Date() +" before deleteFile." );
            log.info(new Date() +" after deleteFile." );
            // 返回location 指定slave清理
            sendLocation(path, out);
            // 返回code
            out.writeInt(0);
            out.writeUTF("Delete File");
            out.flush();
            // 调用删除函数 删除源数据
            FileInfo fileInfo = delete(path,owner);
            log.info(new Date() +" deleteFile 1." );
            // 优化 可以再加个DFS Client 返回删除结果，哪些slave成功删除
            // 删除完毕则无其他操作，否则将deletefileInfo 重插入，并更新locations状态 ， 或重发
        } catch (IOException e) {
            e.printStackTrace();
            try {
                //删除失败时
                out.writeInt(-1);
                out.writeUTF(e.getMessage());
                out.flush();
            } catch (IOException e1) {
                e1.printStackTrace();
            }

        }
    }

    // 删除元数据 文件或目录
    public FileInfo delete(String path, String requester) {
        FileInfo deleteInfo = fileSystem.get(path);
        String owner = deleteInfo.getOwner();
        if (fileSystem.containsKey(path)) {
            // 清除子文件
            if(deleteInfo.getChildren() != null && !deleteInfo.getChildren().isEmpty()){
                System.out.println((deleteInfo.isDirectory() ? "Directory" : "File") + " " + path + " contain files : " + deleteInfo.getChildren());
                //这里注意，因为删除第0个位置的children后，后续的数据会重新从0开始排列
                String filePath = deleteInfo.getChildren().get(0).getPath();
                //循环删除子文件或目录
                delete(filePath,owner);
            }
            // 清除 需要删除文件本身
            if (owner.equals(requester)) {
                //测试
                // 删除文件或目录--元数据
//                FileInfo deleteInfo = fileSystem.get(path);
                deleteInfo.getParent().getChildren().remove(deleteInfo);
                fileSystem.remove(path);
                //测试
                System.out.println((deleteInfo.isDirectory() ? "Directory" : "File") + " " + path + " deleted by " + requester);
                log.info((deleteInfo.isDirectory() ? "Directory" : "File") + " " + path + " deleted by " + requester);
            } else {
                System.out.println("Permission denied. You are not the owner of " + path);
                log.info("Permission denied. You are not the owner of " + path);
            }
        } else {
            System.out.println("File/Directory " + path + " not found.");
            log.info("File/Directory " + path + " not found.");
        }
        return  deleteInfo;
    }
    private void renameFile(DataInputStream in, DataOutputStream out) {
        try {
            String path = in.readUTF(); // 读取客户端发送路径
            String newName = in.readUTF(); // 读取客户端发送用户

            FileInfo renameInfo = fileSystem.get(path);

            for (Map.Entry<String, FileInfo> entry : fileSystem.entrySet()) {
                System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
            }
            FileInfo fileInfo = rename(path,newName);
            //输出rename结果
            out.writeInt(0);
            out.flush();

            for (Map.Entry<String, FileInfo> entry : fileSystem.entrySet()) {
                System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
            }

        } catch (IOException e) {
            e.printStackTrace();
            try {
                out.writeInt(-1);
                out.writeUTF(e.getMessage());
                out.flush();
            } catch (IOException e1) {
                e1.printStackTrace();
            }

        }
    }

    public FileInfo rename( String path, String newName) {
        FileInfo renameInfo = fileSystem.get(path);

        if (fileSystem.containsKey(path)) {
            //测试
            // 重命名文件或目录--元数据
            String newNamePath = '/' + newName;
            FileInfo parentFileInfo = renameInfo.getParent();
//            System.out.println(renameInfo.getFileName());
            //如果存在父文件夹
            if (!parentFileInfo.getFileName().isEmpty()){
                newNamePath = parentFileInfo.getPath()+ '/' + newName;
                //test
//                System.out.println(newNamePath);
            }
            renameInfo.setCreationTime(System.currentTimeMillis());
            renameInfo.setFileName(newName);
            renameInfo.setPath(newNamePath);
            //重新映射元数据
            fileSystem.remove(path);
//            renameInfo.getParent().getChildren().remove(renameInfo);
            fileSystem.put(newNamePath,renameInfo);
            //递归的修改文件映射
            if(renameInfo.getChildren() != null && !renameInfo.getChildren().isEmpty()){
                //循环修改子文件映射
                for (int i = 0; i < renameInfo.getChildren().size(); i ++){
//                    System.out.println(renameInfo.getChildren());
                    String filePath = renameInfo.getChildren().get(i).getPath();
                    String childNewName = renameInfo.getChildren().get(i).getFileName();
                    //测试chile
//                System.out.println( filePath + "   " + childNewName);
                    //循环修改子文件或目录
                    rename(filePath,childNewName);
                }
            }

        } else {
            System.out.println("File/Directory " + path + " not found.");
            log.info("File/Directory " + path + " not found.");
        }
        return  renameInfo;
    }

    // 获取文件或目录信息
    public FileInfo getFileInfo(String path) {
        FileInfo fileInfo = null;
        if (fileSystem.containsKey(path)) {
            fileInfo = fileSystem.get(path);
            System.out.print("Path: " + path);
            System.out.print( " Owner: " + fileInfo.getOwner());
            System.out.println(" Is Directory: " + fileInfo.isDirectory());
            //测试
//            System.out.println(" children: " + fileInfo.getChildren());
        } else {
            System.out.println(" File/Directory " + path + " not found.");
        }
        return fileInfo;
    }

    // 获取父目录路径
    private String getParentPath(String path) {
        int lastSeparatorIndex = path.lastIndexOf('/');
        if (lastSeparatorIndex == -1) {
            return "/";
        } else {
            if(lastSeparatorIndex == 0){
                return "/";
            }
            return path.substring(0, lastSeparatorIndex);
        }
    }
    //删除文件夹及其所包含的所有子目录和文件
    public  void deleteDirectory(File file) {
        if (!file.exists()) {
            return;
        }
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File subFile : files) {
                    deleteDirectory(subFile);
                }
            }
        }
        //最后删除操作
        file.delete();
        System.out.println("Deleted file/folder: " + file.getAbsolutePath());
    }

    // 获取文件对应的存储节点
    public String getStorageNode(String filePath) {
        return fileToStorageNode.get(filePath);
    }

    public String getNewStorageNode(long fileSize) {
        Random random = new Random();
        //根据现有大小获取随机值
        String[] keys = storageNodes.keySet().toArray(new String[0]);
        String randomKey = keys[random.nextInt(keys.length)];
        StorageNode randomValue = storageNodes.get(randomKey);
        return randomValue.getName();
    }

    public void setStorageNode(List<StorageNode> storageNodes) {
        for(StorageNode sn : storageNodes)
            this.storageNodes.put(sn.getName(),sn);
    }


    private void listFile(DataInputStream in, DataOutputStream out) {
        try {
            List<String> fileNameList = new ArrayList<>();
            System.out.println("我是MetadataServer");
            String cur_dir = in.readUTF();
            //getFileInfo--产生输出
            FileInfo fileInfo = getFileInfo(cur_dir);

            System.out.println("Sever List...");
            if(fileInfo != null){
                if (fileInfo.getChildren() != null){
                    for(FileInfo ch:fileInfo.getChildren()){
                        fileNameList.add(ch.getFileName());
                        //测试输出
                        System.out.println(" name:" + ch.getFileName() + "  isDirectory:" + ch.isDirectory());

                    }
                }else{
                    System.out.println(fileInfo.getFileName() + "  is empty!");
                }
                int size = fileNameList.size();
                //将大小发给客户端
                out.writeInt(size);
                out.flush();
                System.out.println(" size:" + size);
                for (String name : fileNameList) {
                    out.writeUTF(name);
                }
                if (size > 0) out.flush();


                System.out.println("end of listfile.");
            }else{
                //查找路径不存在
                System.out.println(" name:" + cur_dir + " is not exist." );
                out.writeInt(-1);
                out.flush();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    // slave心跳
    private void handleHeartBeat(DataInputStream in, DataOutputStream out) {
        try {
            String nodeName = in.readUTF(); // 读取数据服务器发送的注册信息
            System.out.println(new Date() +" received heartBeat from DataServer: " + nodeName);

            String msg = nodeName;
            //获取当前时间
            Long now = System.currentTimeMillis();
            //containsKey函数--检查 hashMap 中是否存在指定的 key 对应的映射关系。
            if(this.storageNodes.containsKey(nodeName)){
                if(!storageNodesUpTime.containsKey(nodeName)){
                    storageNodesUpTime.put(nodeName,now);
                    msg += " time out information is registered.";
                    System.out.println(msg);
                }

                Long lastUpTime = storageNodesUpTime.get(nodeName);
                //规定心跳时间未响应，连接超时
                if(now - lastUpTime > (long) Config.TIMEOUT_OF_HEARTBEATS * Config.HEARTBEAT_SECS * 1000){
                    msg += " （Connection timed out）| time out is recovered.";
                    System.out.println(msg);
                }else{
                    storageNodesUpTime.put(nodeName,now);//更新时间
                }
            }

            // 处理心跳信息并回复数据服务器
            out.writeInt(0);
            out.writeUTF(msg);
        }catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }

    // DFS客户端退出
    public boolean DFS_Client_Exit(DataInputStream in, DataOutputStream out) {
        boolean res = false;
        try {
            String user = in.readUTF();
            res = saveMetaPoint();    // 是否Image成功
            if (res) {
                out.writeInt(0);
                out.writeUTF("SaveImage");
                // 关闭通信
                in.close();
                out.close();
                System.out.println("DFS Client < user: "+user+" >\t Exit Success.");
            } else {
                out.writeInt(-1);
                out.writeUTF("SaveImageERROR");
                System.out.println("ERROR: DFS Client < user: "+user+" >\t Exit Failed!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }
    // 在结束前执行的 元数据持久化更新
    public boolean saveMetaPoint() {
        boolean res = metaHandler.saveFileSystem(fileSystem, Config.META_STORE_PATH);
        if (res) {
            System.out.println("------------------------------\n>>> Info : Image saved successful! -> "+Config.META_STORE_PATH+"\n------------------------------");
        } else {
            System.out.println("------------------------------\n>>> Error : Image saved Failed! -> "+"\n------------------------------");
        }
        return res;
    }

    public static void main(String[] args) {
        MetadataServer metaServer = new MetadataServer(Config.META_STORE_PATH, Config.WORKER_PATH);
        try {
            // 挂载定时更新 fileSystem镜像 任务
            new CycleTask("SystemImageSave", 60, new Runnable() {
                @Override
                public void run() {
                    metaServer.saveMetaPoint();
                }
            }).start();
            // 开启服务
            metaServer.serve();
        } catch (Exception e) {
            e.printStackTrace();
            metaServer.saveMetaPoint(); // 保存镜像
        }
    }

}

